<?php

uses(APP_DIR . '/core/form/actions');
uses(APP_DIR . '/core/form/elements');
uses(APP_DIR . '/core/form/filters');

require_once(dirname(__file__) . '/exceptions.php');

/**
 *
 *
 * @property string pk
 *
 */
class Form extends AbstractRenderable
{

  const ACTION_NONE = 0;

  const ACTION_INDEX = 1;

  const ACTION_VIEW = 2;

  const ACTION_EDIT = 4;

  const ACTION_CREATE = 8;

  const ACTION_DELETE = 16;

  const ACTION_ALL = 31;

  const ACTION_CRUD = 31;

  const ACTION_OPTIONS = 32;

  const ACTION_CALENDAR = 64;

  const ACTION_IMAGES = 128;

  public static $actionCode = array('index' => 1, 'view' => 2, 'edit' => 4, 'create' => 8, 'delete' => 16, 'options' => 32, 'calendar' => 64, 'images' => 128);

  public static $actionValue = array(1 => 'index', 2 => 'view', 4 => 'edit', 8 => 'create', 16 => 'delete', 32 => 'options', 64 => 'calendar', 128 => 'images');

  public $table;

  public $title;

  public $orderField;

  public $route;

  public $routeParams;

  public $iteratorClass = 'DataViewIterator';

  public $viewClass = 'DataView';

  public $actionMask = 0;

  public $parent;

  /**
   * @var array
   */
  protected $filters = array();

  /**
   *
   * @return IRepository
   */
  protected $gateway;

  protected $id;

  protected $elements = array();

  protected $elementsIterator = array();

  protected $sortable;

  /**
   * @var array
   */
  protected $actions = array();

  /**
   *
   * @var Menu
   */
  public $menu;

  public function setId($id)
  {
    $this->id = $id;
  }

  public function setParent($table, $pk, $fk, $label, $redirect)
  {
    $this->parent = array('table' => $table, 'pk' => $pk, 'fk' => $fk, 'label' => $label, 'redirect' => $redirect);
  }

  public function setRecursivity($parent, $left, $right, $label)
  {
    $tree = new FormTree($this->gateway(), $parent, $left, $right);

    $id = s::request()->get($this->pk);

    $formAction = s::request()->get('formAction');

    if ($formAction == 'sort') {
      $index = s::request()->get('sortIndex');
      $tree->move($id, $index);
    }

    $children = new FormElement_Link($this->pk, 'Children');
    $children->foreignKey = $parent;
    $children->url = $this->getRoute();

    $parent_id = $tree->getParentId();

    $_parent = $this->gateway()->find($parent_id);

    $parentSelect = new FormElement_Select($parent, 'Parent');
    $parentSelect->emptyValue = 0;
    $parentSelect->emptyLabel = 'None';
    $parentSelect->keyField = $this->pk;
    $parentSelect->labelField = $label;
    $parentSelect->defaultValue = $parent_id;

    $this->addElement($children, Form::ACTION_INDEX | Form::ACTION_VIEW);
    $this->addElement($parentSelect, Form::ACTION_ALL ^ Form::ACTION_INDEX);

    if (! empty($_parent)) {
      $sql = "
        SELECT parent.$this->pk, parent.$label, parent.$parent
        FROM $this->table AS node, $this->table AS parent
        WHERE (node.$left BETWEEN parent.$left AND parent.$right)
          AND node.$this->pk = $parent_id
        ORDER BY parent.$left
      ";

      $path = DB::getInstance()->query($sql)->execute()->fetchAll();

      $this->getAction(Form::ACTION_INDEX)->set('path', $path);
      $this->getAction(Form::ACTION_INDEX)->set('parent', $parent);
      $this->getAction(Form::ACTION_INDEX)->set('label', $label);
    }

    if ($id) {
      $row = $this->gateway()->find($id, array('select' => array($left, $right)));

      $left_id = $row[$left];
      $right_id = $row[$right];

      $where = "AND (node.$left NOT BETWEEN $left_id AND $right_id)";
    }

    $sql = "
      SELECT node.$this->pk,
        CONCAT(REPEAT('&ndash;', (COUNT(parent.$this->pk) - 1)), ' ', node.$label) AS $label,
        (COUNT(parent.$this->pk) - 1) AS depth
      FROM $this->table AS node, $this->table AS parent
      WHERE (node.$left BETWEEN parent.$left AND parent.$right) $where
      GROUP BY node.$this->pk
      ORDER BY node.$left
    ";

    $data = DB::getInstance()->query($sql)->execute()->fetchAll();

    $parentSelect->options = sy_array_to_options($data, $this->pk, $label);

    s::response()->getParams()->set($parent, $parent_id);

    $this->gateway()->behaviors['tree'] = $tree;
  }

  public function getRoute()
  {
    if (empty($this->route)) {
      $this->route = s::request()->route();
    }

    return $this->route;
  }

  public function setRoute($route, $routeParams = null)
  {
    $this->route = $route;
    $this->routeParams = $routeParams;
  }

  public function getId($method = 'GET')
  {
    if (empty($this->id)) {
      $this->id = s::request()->get($this->pk);
    }

    return $this->id;
  }

  public function execute($action)
  {
    if (! $this->show($action)) {
      throw new FormNoSuchActionException('No such action');
    }

    if ($this->parent) {
      $val = s::request()->get($this->parent['fk']);

      if (empty($val)) {
        s::getInstance()->warnings('Select the ' . $this->parent['table']);
        s::response()->redirect($this->parent['redirect']);
      }

      s::response()->getParams()->set($this->parent['fk'], $val);
    }

    foreach ($this->filters as $filter) {
      $filter->execute();
    }

    $Action = $this->getAction($action);

    $Action->executeCallback('beforeAction');
    $Action->executeCallback('before' . $Action->getName());

    $response = $Action->execute();

    $Action->executeCallback('after' . $Action->getName());
    $Action->executeCallback('afterAction');

    return $response;
  }

  public function render($action)
  {
    global $config;

    if (! $this->show($action)) {
      throw new FormNoSuchActionException('No such action');
    }

    $Action = $this->getAction($action);

    $content = $Action->render();

    if (! s::request()->ajax() && ! s::request()->json()) {
      $this->set('form', $this);
      $this->set('menu', $this->getMenu($action));
      $this->set('module_title', $this->title);
      $this->set('route', $this->getRoute());
      $this->set('form_body', $content);

      return $this->getView();
    }

    return $content;
  }

  public function getMenu($action)
  {
    if (is_null($this->menu)) {
      if (($action & Form::ACTION_OPTIONS) != $action) {
        $this->menu = new Menu();

        if ($this->show(Form::ACTION_INDEX)) {
          $this->menu->addItem(new MenuItem('list', array('List', 'index'), $this->getRoute()));
        }

        if ($this->show(Form::ACTION_CREATE)) {
          $this->menu->addItem(new MenuItem('create', array('Create', 'create'), array($this->getRoute(), array('action' => 'create'))));
        }
      }
    }

    return $this->menu;
  }

  public function getTemplate()
  {
    global $config;

    $template = parent::getTemplate();

    if (is_null($template)) {
      $template = $config['views_dir'] .  '/form/form_layout.php';
    }

    return $template;
  }

  /**
   *
   * @return FormAction
   */
  public function getAction($action)
  {
    if (empty($this->actions[$action])) {
      $class = 'FormAction_' . Inflector::camelize(self::$actionValue[$action]);
      $this->actions[$action] = new $class($this);
    }

    return $this->actions[$action];
  }

  public function addFilter(FormFilter $element)
  {
    $element->form = $this;

    $this->filters[] = $element;

    return $element;
  }

  public function getFilters()
  {
    return $this->filters;
  }

  public function getVisibleFilters()
  {
    $filters = array();

    foreach ($this->filters as $filter) {
      if ($filter->visible) {
        $filters[] = $filter;
      }
    }

    return ! empty($filters) ? $filters : null;
  }

  /**
   *
   * @param string $name
   * @param FormElement $element
   * @return FormElement
   */
  public function addElement(FormComponent $element, $actions = Form::ACTION_ALL)
  {
    $element->form = $this;
    $element->add = $actions;

    $this->elements[] = $element;

    $this->actionMask = $this->actionMask | $actions;

    return $element;
  }

  /**
   *
   * @return FormComponentIterator
   */
  public function getElements($action = null, $reset = true)
  {
    if (empty($this->elementsIterator[$action])) {
      $this->elementsIterator[$action] = new FormComponentIterator($this->elements, $action);
    }

    if ($reset) {
      $this->elementsIterator[$action]->reset();
    }

    return $this->elementsIterator[$action];
  }

  /**
   *
   * @return IRepository
   */
  public function gateway($gateway = null)
  {
    if ($gateway instanceof IRepository) {
      $this->gateway = $gateway;
    }

    if (empty($this->gateway)) {
      $this->gateway = FormDataGateway::getInstance($this->table, $this->pk);

      if ($this->parent) {
        $this->gateway->filter = $this->parent['fk'] . ' = ' . s::request()->get($this->parent['fk']);
      }

      if ($this->orderField) {
        $this->gateway->behaviors['sort'] = new FormSortable($this->gateway, $this->orderField);
      }
    }

    return $this->gateway;
  }

  /**
   *
   * @return boolean
   */
  public function show($action)
  {
    return ($this->actionMask & $action) == $action;
  }

}

?>